package client;

import codec.Netty4ProtocolDecoder;
import codec.Netty4ProtocolEncoder;
import handler.Netty4ClientHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.DefaultThreadFactory;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
@Slf4j
public class Netty4ClientFactory extends AbstractClientFactory {
    private static final AbstractClientFactory self = new Netty4ClientFactory();

    private static final NioEventLoopGroup group = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors(), new DefaultThreadFactory("client_loop_group"));
    private Netty4ClientFactory() {
    }
    public static AbstractClientFactory getInstance() {
        return self;
    }
    @Override
    protected Client createClient(String targetIp, int port, int connectionTimeout) throws Exception {
        String key = targetIp + ":" + port;
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.ALLOCATOR, ByteBufAllocator.DEFAULT)
                .option(ChannelOption.TCP_NODELAY, true)
                .option(ChannelOption.SO_REUSEADDR, true);
        if (connectionTimeout < 1000) {
            bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000);
        } else {
            bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeout);
        }
        Netty4ClientHandler netty4ClientHandler = new Netty4ClientHandler(this, key);
        bootstrap.handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                pipeline.addLast(new Netty4ProtocolEncoder());
                pipeline.addLast(new Netty4ProtocolDecoder());
                pipeline.addLast(netty4ClientHandler);

            }
        });
        ChannelFuture future = bootstrap.connect(new InetSocketAddress(targetIp, port)).sync();
        future.awaitUninterruptibly(connectionTimeout);
        if (!future.isDone()) {
            log.error("Create connection to " + targetIp + ":" + port + " timeout!");
            throw new Exception("Create connection to " + targetIp + ":" + port + " timeout!");
        }
        if (future.isCancelled()) {
            log.error("Create connection to " + targetIp + ":" + port + " cancelled by user!");
            throw new Exception("Create connection to " + targetIp + ":" + port + " cancelled by user!");
        }
        if (!future.isSuccess()) {
            log.error("Create connection to " + targetIp + ":" + port + " error", future.cause());
            throw new Exception("Create connection to " + targetIp + ":" + port + " error", future.cause());
        }
        Netty4Client netty4Client = new Netty4Client(future, key, connectionTimeout);
        netty4ClientHandler.setClient(netty4Client);
        return netty4Client;
    }
}
